﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;

namespace RoadPlot
{
    //http://msdn.microsoft.com/en-us/magazine/dd263097.aspx

    public enum EnumCurveyTerminus { Start, Middle, End };
    public class CurvyText
    {
        readonly List<TextBlock> _textBlocks = new List<TextBlock>();

        private static double GetPathFigureLength(PathFigure pathFigure)
        {
            if (pathFigure == null)
                return 0;

            bool isAlreadyFlattened = pathFigure.Segments.All(pathSegment => (pathSegment is PolyLineSegment) || (pathSegment is LineSegment));

            var pathFigureFlattened = isAlreadyFlattened ? pathFigure : pathFigure.GetFlattenedPathFigure();
            double length = 0;
            var pt1 = pathFigureFlattened.StartPoint;

            foreach (PathSegment pathSegment in pathFigureFlattened.Segments)
            {
                if (pathSegment is LineSegment)
                {
                    Point pt2 = (pathSegment as LineSegment).Point;
                    length += (pt2 - pt1).Length;
                    pt1 = pt2;
                }
                else if (pathSegment is PolyLineSegment)
                {
                    PointCollection pointCollection = (pathSegment as PolyLineSegment).Points;
                    foreach (Point pt2 in pointCollection)
                    {
                        length += (pt2 - pt1).Length;
                        pt1 = pt2;
                    }
                }
            }
            return length;
        }

        public void PrepareCurvyText(PathGeometry myPathGeometry, double baseLineTrim,
            FontFamily fontFamily, double fontSize, RoadLegend legend)
        {
            _textBlocks.Clear();

            double totalStringLength = 0;

            IEnumerable<TextBlock> textBlocks = UpdateText(legend._text, fontFamily, fontSize, ref totalStringLength);

            double progressFactor = legend._offset;
            double pathLength = myPathGeometry.Figures.Sum(pathFigure => GetPathFigureLength(pathFigure));

            double residualLength = pathLength - totalStringLength;

            progressFactor += legend._leadingBlank / pathLength;

            switch (legend._enumCurveyTerminus)
            {
                case EnumCurveyTerminus.Start:
                    progressFactor = legend._offset;

                    // only display start text if there's enough room
                    if (totalStringLength > pathLength)
                        return;

                    break;

                case EnumCurveyTerminus.End:
                    progressFactor = ((pathLength - totalStringLength) - (legend._offset * residualLength)) / pathLength;

                    // only display end text if there's enough room for the start text as well
                    if (totalStringLength > pathLength / 2)
                        return;

                    break;

                case EnumCurveyTerminus.Middle:
                    progressFactor = legend._offset - (totalStringLength / 2 / pathLength);

                    // only display middle text if there's enough room for the 2 ends
                    if (totalStringLength > pathLength / 3)
                        return;

                    break;
            }

            Point pointStart, tangentStart, pointEnd, tangentEnd;
            myPathGeometry.GetPointAtFractionLength(progressFactor, out pointStart, out tangentStart);
            myPathGeometry.GetPointAtFractionLength(progressFactor + totalStringLength, out pointEnd, out tangentEnd);

            double alpha = Math.Atan2(pointStart.Y - pointEnd.Y, pointEnd.X - pointStart.X);

            bool flip = false;
            if (alpha < -1.2 || alpha > 2)
            {
                textBlocks = textBlocks.Reverse();
                flip = true;
            }

            foreach (var child in textBlocks)
            {
                if (progressFactor >= 1.0)
                    break;

                _textBlocks.Add(child);

                double width = child.DesiredSize.Width;

                progressFactor += width / 2 / pathLength;

                Point point, tangent;

                myPathGeometry.GetPointAtFractionLength(progressFactor, out point, out tangent);

                var transformGroup = new TransformGroup();

                if (flip)
                {
                    transformGroup.Children.Add(new RotateTransform(180, width / 2, baseLineTrim));
                }
                transformGroup.Children.Add(
                    new RotateTransform(Math.Atan2(tangent.Y, tangent.X) * 180 / Math.PI, width / 2, baseLineTrim));
                transformGroup.Children.Add(
                    new TranslateTransform(point.X - width / 2, point.Y - baseLineTrim));

                child.RenderTransform = transformGroup;

                progressFactor += width / 2 / pathLength;
            }
        }

        private const string LAO_ADORNERS = "ັຳິີຶືຼົູຸ່້໊໋໌ໍ";

        static bool IsAdorner(char ch)
        {
            return LAO_ADORNERS.IndexOf(ch) != -1;
        }

        static IEnumerable<string> ChunkLao(IEnumerable<char> text)
        {
            var ret = new List<string>();

            var sb = new StringBuilder();
            foreach (var ch in text)
            {
                if (IsAdorner(ch))
                {
                    sb.Append(ch);
                }
                else
                {
                    ret.Add(sb.ToString());
                    sb = new StringBuilder();
                    sb.Append(ch);
                }
            }
            ret.Add(sb.ToString());
            return ret;
        }

        private static IEnumerable<TextBlock> UpdateText(IEnumerable<char> text, FontFamily fontFamily, double fontSize, ref double total)
        {
            var textBlocks = new List<TextBlock>();

            IEnumerable<string> laoChunks = ChunkLao(text);
            foreach (var ch in laoChunks)
            {
                var t = new TextBlock
                {
                    FontSize = fontSize,
                    FontFamily = fontFamily,
                    Text = "" + ch,
                };

                t.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                total += t.DesiredSize.Width;
                textBlocks.Add(t);
            }
            return textBlocks;
        }


        public void AddCurvyTextBlocks(UIElementCollection children)
        {
            foreach (var child in _textBlocks)
            {
                children.Add(child);
            }
        }
    }
}
